package com.cn.stepLock.chain;

import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Aaron
 * @desc
 * @createTime 2022-02-23 1:13
 * @since
 */
public class LinkedBlockBothWayTaskQueue {

    private final int capacity = Integer.MAX_VALUE;

    /** Current number of elements */
    private final AtomicInteger count = new AtomicInteger();

    /**
     * Head of linked list.
     * Invariant: head.item == null
     */
    transient Task head;

    /**
     * Tail of linked list.
     * Invariant: last.next == null
     */
    private transient Task last;

    /** Lock held by take, poll, etc */
    private final ReentrantLock takeLock = new ReentrantLock();

    /** Wait queue for waiting takes */
    private final Condition notEmpty = takeLock.newCondition();

    /** Lock held by put, offer, etc */
    private final ReentrantLock putLock = new ReentrantLock();

    private final ReentrantLock notEmptyParkChain = new ReentrantLock();

    /** Wait queue for waiting puts */
    //private final Condition notFull = putLock.newCondition();

    AtomicInteger exceptTaskCount;
    /**
     * Signals a waiting take. Called only from put/offer (which do not
     * otherwise ordinarily lock takeLock.)
     */
    private void signalNotEmpty() {
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
    }

    /**
     * Signals a waiting put. Called only from take/poll.
     */
    private void signalNotFull() {
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            //notFull.signal();
        } finally {
            putLock.unlock();
        }
    }

    private int enqueue(Task task) {
        last.addQueueTask(task);
        last = task;
        return this.count.getAndIncrement();
    }

    private Task dequeue() {
        Task h = head;
        Task first = h.nextQueueTask;
        h.nextQueueTask = h; // help GC
        head = first;
        Task x = first;
        first.preQueueTask = null;
        return x;
    }

    private Task dequeueLast() {
        Task task = last;
        last = task.preQueueTask;
        last.nextQueueTask = null;
        task.preQueueTask =  null;
        return task;
    }

    /**
     * Locks to prevent both puts and takes.
     */
    void fullyLock() {
        putLock.lock();
        takeLock.lock();
    }

    /**
     * Unlocks to allow both puts and takes.
     */
    void fullyUnlock() {
        takeLock.unlock();
        putLock.unlock();
    }


    public LinkedBlockBothWayTaskQueue() {
        last  = head = new Task();
    }

    public int size() {
        return count.get();
    }

    // this doc comment is a modified copy of the inherited doc comment,
    // without the reference to unlimited queues.
    /**
     * Returns the number of additional elements that this queue can ideally
     * (in the absence of memory or resource constraints) accept without
     * blocking. This is always equal to the initial capacity of this queue
     * less the current {@code size} of this queue.
     *
     * <p>Note that you <em>cannot</em> always tell if an attempt to insert
     * an element will succeed by inspecting {@code remainingCapacity}
     * because it may be the case that another thread is about to
     * insert or remove an element.
     */
    public int remainingCapacity() {
        return capacity - count.get();
    }

    /**
     * Inserts the specified element at the tail of this queue if it is
     * possible to do so immediately without exceeding the queue's capacity,
     * returning {@code true} upon success and {@code false} if this queue
     * is full.
     * When using a capacity-restricted queue, this method is generally
     * preferable to method {@link BlockingQueue#add add}, which can fail to
     * insert an element only by throwing an exception.
     *
     * @throws NullPointerException if the specified element is null
     */
    public boolean offer(Task task) {
        if (task == null) throw new NullPointerException();
        final AtomicInteger count = this.count;
        if (count.get() == capacity)
            return false;
        int c = -1;
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            if (count.get() < capacity) {
                c = enqueue(task);
                //c = count.getAndIncrement();
//                if (c + 1 < capacity)
                    //notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
        return c >= 0;
    }

    public boolean offerPark(Task task) {
        if (task == null) throw new NullPointerException();
        final AtomicInteger count = this.count;
        if (count.get() == capacity)
            return false;
        int c = -1;
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            if (count.get() < capacity) {
                c = enqueue(task);
                //c = count.getAndIncrement();
//                if (c + 1 < capacity)
                //notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
        if (c == exceptTaskCount.get()-1)
            signalNotEmpty();
        return c >= 0;
    }

    public Task take() throws InterruptedException {
        Task x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

    public Task pollPark() {
        final AtomicInteger count = this.count;
        if (count.get() == 0)
            return null;
        Task x = null;
        int c = -1;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            if (count.get() > 0) {
                x = dequeue();
                c = count.getAndDecrement();
//                if (c > 1)
//                    notEmpty.signal();
            }
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

    public Task takeLast() throws InterruptedException {
        Task x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (exceptTaskCount  ==null || count.get() < exceptTaskCount.get()) {
                notEmpty.await();
            }
            x = dequeueLast();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }




}
